{
 "cells": [
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "\n",
    "\n",
    "# BEGIN DESCR_KINDS\n",
    "\n",
    "### auxiliary functions for display only ###\n",
    "\n",
    "def cls_name(obj_or_cls):\n",
    "    cls = type(obj_or_cls)\n",
    "    if cls is type:\n",
    "        cls = obj_or_cls\n",
    "    return cls.__name__.split('.')[-1]\n",
    "\n",
    "def display(obj):\n",
    "    cls = type(obj)\n",
    "    if cls is type:\n",
    "        return '<class {}>'.format(obj.__name__)\n",
    "    elif cls in [type(None), int]:\n",
    "        return repr(obj)\n",
    "    else:\n",
    "        return '<{} object>'.format(cls_name(obj))\n",
    "\n",
    "def print_args(name, *args):\n",
    "    pseudo_args = ', '.join(display(x) for x in args)\n",
    "    print('-> {}.__{}__({})'.format(cls_name(args[0]), name, pseudo_args))\n",
    "\n",
    "\n",
    "### essential classes for this example ###\n",
    "\n",
    "class Overriding:  # <1>\n",
    "    \"\"\"a.k.a. data descriptor or enforced descriptor\"\"\"\n",
    "\n",
    "    def __get__(self, instance, owner):\n",
    "        print_args('get', self, instance, owner)  # <2>\n",
    "\n",
    "    def __set__(self, instance, value):\n",
    "        print_args('set', self, instance, value)\n",
    "\n",
    "\n",
    "class OverridingNoGet:  # <3>\n",
    "    \"\"\"an overriding descriptor without ``__get__``\"\"\"\n",
    "\n",
    "    def __set__(self, instance, value):\n",
    "        print_args('set', self, instance, value)\n",
    "\n",
    "\n",
    "class NonOverriding:  # <4>\n",
    "    \"\"\"a.k.a. non-data or shadowable descriptor\"\"\"\n",
    "\n",
    "    def __get__(self, instance, owner):\n",
    "        print_args('get', self, instance, owner)\n",
    "\n",
    "\n",
    "class Managed:  # <5>\n",
    "    over = Overriding()\n",
    "    over_no_get = OverridingNoGet()\n",
    "    non_over = NonOverriding()\n",
    "\n",
    "    def spam(self):  # <6>\n",
    "        print('-> Managed.spam({})'.format(display(self)))\n",
    "\n",
    "# END DESCR_KINDS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Overriding descriptor (a.k.a. data descriptor or enforced descriptor):\n",
    "\n",
    "# BEGIN DESCR_KINDS_DEMO1\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj = Managed()  # <1>\n",
    "obj.over  # <2>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Managed.over  # <3>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over = 7  # <4>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over  # <5>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.__dict__['over'] = 8  # <6>\n",
    "vars(obj)  # <7>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over  # <8>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "# END DESCR_KINDS_DEMO1\n",
    "\n",
    "Overriding descriptor without ``__get__``:\n",
    "\n",
    "(these tests are reproduced below without +ELLIPSIS directives for inclusion in the book;\n",
    "look for DESCR_KINDS_DEMO2)\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Managed.over_no_get  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get = 7"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.__dict__['over_no_get'] = 9\n",
    "obj.over_no_get"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get = 7"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Non-overriding descriptor (a.k.a. non-data descriptor or shadowable descriptor):\n",
    "\n",
    "# BEGIN DESCR_KINDS_DEMO3\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj = Managed()\n",
    "obj.non_over  # <1>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.non_over = 7  # <2>\n",
    "obj.non_over  # <3>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Managed.non_over  # <4>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "del obj.non_over  # <5>\n",
    "obj.non_over  # <6>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "# END DESCR_KINDS_DEMO3\n",
    "\n",
    "No descriptor type survives being overwritten on the class itself:\n",
    "\n",
    "# BEGIN DESCR_KINDS_DEMO4\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj = Managed()  # <1>\n",
    "Managed.over = 1  # <2>\n",
    "Managed.over_no_get = 2\n",
    "Managed.non_over = 3\n",
    "obj.over, obj.over_no_get, obj.non_over  # <3>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "# END DESCR_KINDS_DEMO4\n",
    "\n",
    "Methods are non-overriding descriptors:\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.spam  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Managed.spam  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.spam()"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Managed.spam()"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "      ...\n",
    "    TypeError: spam() missing 1 required positional argument: 'self'\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Managed.spam(obj)"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Managed.spam.__get__(obj)  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.spam.__func__ is Managed.spam"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.spam = 7\n",
    "obj.spam"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "NOTE: These tests are here because I can't add callouts after +ELLIPSIS\n",
    "directives and if doctest runs them without +ELLIPSIS I get test failures.\n",
    "\n",
    "# BEGIN DESCR_KINDS_DEMO2\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get  # <1>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Managed.over_no_get  # <2>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get = 7  # <3>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get  # <4>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.__dict__['over_no_get'] = 9  # <5>\n",
    "obj.over_no_get  # <6>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get = 7  # <7>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get  # <8>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "# END DESCR_KINDS_DEMO2\n",
    "\n",
    "Methods are non-overriding descriptors:\n",
    "\n",
    "# BEGIN DESCR_KINDS_DEMO5\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj = Managed()\n",
    "obj.spam  # <1>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Managed.spam  # <2>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.spam = 7  # <3>\n",
    "obj.spam"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "# END DESCR_KINDS_DEMO5\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "\n",
    "# BEGIN DESCRIPTORKINDS\n",
    "def print_args(name, *args):  # <1>\n",
    "    cls_name = args[0].__class__.__name__\n",
    "    arg_names = ['self', 'instance', 'owner']\n",
    "    if name == 'set':\n",
    "        arg_names[-1] = 'value'\n",
    "    print('{}.__{}__() invoked with args:'.format(cls_name, name))\n",
    "    for arg_name, value in zip(arg_names, args):\n",
    "        print('    {:8} = {}'.format(arg_name, value))\n",
    "\n",
    "\n",
    "class Overriding:  # <2>\n",
    "    \"\"\"a.k.a. data descriptor or enforced descriptor\"\"\"\n",
    "\n",
    "    def __get__(self, instance, owner):\n",
    "        print_args('get', self, instance, owner)  # <3>\n",
    "\n",
    "    def __set__(self, instance, value):\n",
    "        print_args('set', self, instance, value)\n",
    "\n",
    "\n",
    "class OverridingNoGet:  # <4>\n",
    "    \"\"\"an overriding descriptor without ``__get__``\"\"\"\n",
    "\n",
    "    def __set__(self, instance, value):\n",
    "        print_args('set', self, instance, value)\n",
    "\n",
    "\n",
    "class NonOverriding:  # <5>\n",
    "    \"\"\"a.k.a. non-data or shadowable descriptor\"\"\"\n",
    "\n",
    "    def __get__(self, instance, owner):\n",
    "        print_args('get', self, instance, owner)\n",
    "\n",
    "\n",
    "class Model:  # <6>\n",
    "    over = Overriding()\n",
    "    over_no_get = OverridingNoGet()\n",
    "    non_over = NonOverriding()\n",
    "\n",
    "    def spam(self):  # <7>\n",
    "        print('Model.spam() invoked with arg:')\n",
    "        print('    self =', self)\n",
    "\n",
    "#END DESCRIPTORKINDS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Overriding descriptor (a.k.a. data descriptor or enforced descriptor):\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj = Model()\n",
    "obj.over  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        self     = <descriptorkinds.Overriding object at 0x...>\n",
    "        instance = <descriptorkinds.Model object at 0x...>\n",
    "        owner    = <class 'descriptorkinds.Model'>\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Model.over  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        self     = <descriptorkinds.Overriding object at 0x...>\n",
    "        instance = None\n",
    "        owner    = <class 'descriptorkinds.Model'>\n",
    "\n",
    "\n",
    "An overriding descriptor cannot be shadowed by assigning to an instance:\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj = Model()\n",
    "obj.over = 7  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        self     = <descriptorkinds.Overriding object at 0x...>\n",
    "        instance = <descriptorkinds.Model object at 0x...>\n",
    "        value    = 7\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        self     = <descriptorkinds.Overriding object at 0x...>\n",
    "        instance = <descriptorkinds.Model object at 0x...>\n",
    "        owner    = <class 'descriptorkinds.Model'>\n",
    "\n",
    "\n",
    "Not even by poking the attribute into the instance ``__dict__``:\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.__dict__['over'] = 8\n",
    "obj.over  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        self     = <descriptorkinds.Overriding object at 0x...>\n",
    "        instance = <descriptorkinds.Model object at 0x...>\n",
    "        owner    = <class 'descriptorkinds.Model'>\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "vars(obj)"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Overriding descriptor without ``__get__``:\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Model.over_no_get   # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get = 7  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        self     = <descriptorkinds.OverridingNoGet object at 0x...>\n",
    "        instance = <descriptorkinds.Model object at 0x...>\n",
    "        value    = 7\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Poking the attribute into the instance ``__dict__`` means you can read the new\n",
    "value for the attribute, but setting it still triggers ``__set__``:\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.__dict__['over_no_get'] = 9\n",
    "obj.over_no_get"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get = 7  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        self     = <descriptorkinds.OverridingNoGet object at 0x...>\n",
    "        instance = <descriptorkinds.Model object at 0x...>\n",
    "        value    = 7\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.over_no_get"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Non-overriding descriptor (a.k.a. non-data descriptor or shadowable descriptor):\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj = Model()\n",
    "obj.non_over  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        self     = <descriptorkinds.NonOverriding object at 0x...>\n",
    "        instance = <descriptorkinds.Model object at 0x...>\n",
    "        owner    = <class 'descriptorkinds.Model'>\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Model.non_over  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        self     = <descriptorkinds.NonOverriding object at 0x...>\n",
    "        instance = None\n",
    "        owner    = <class 'descriptorkinds.Model'>\n",
    "\n",
    "\n",
    "A non-overriding descriptor can be shadowed by assigning to an instance:\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.non_over = 7\n",
    "obj.non_over"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Methods are non-over descriptors:\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.spam  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Model.spam  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.spam()  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        self = <descriptorkinds.Model object at 0x...>\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "obj.spam = 7\n",
    "obj.spam"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "No descriptor type survives being overwritten on the class itself:\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Model.over = 1\n",
    "obj.over"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Model.over_no_get = 2\n",
    "obj.over_no_get"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Model.non_over = 3\n",
    "obj.non_over"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "\n",
    "# BEGIN FUNC_DESCRIPTOR_EX\n",
    "import collections\n",
    "\n",
    "\n",
    "class Text(collections.UserString):\n",
    "\n",
    "    def __repr__(self):\n",
    "        return 'Text({!r})'.format(self.data)\n",
    "\n",
    "    def reverse(self):\n",
    "        return self[::-1]\n",
    "\n",
    "# END FUNC_DESCRIPTOR_EX"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "# BEGIN FUNC_DESCRIPTOR_DEMO\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "word = Text('forward')\n",
    "word  # <1>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "word.reverse()  # <2>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Text.reverse(Text('backward'))  # <3>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "type(Text.reverse), type(word.reverse)  # <4>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "list(map(Text.reverse, ['repaid', (10, 20, 30), Text('stressed')]))  # <5>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Text.reverse.__get__(word)  # <6>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "Text.reverse.__get__(None, Text)  # <7>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "word.reverse  # <8>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "word.reverse.__self__  # <9>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "word.reverse.__func__ is Text.reverse  # <10>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "# END FUNC_DESCRIPTOR_DEMO\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "# %load ./bulkfood/bulkfood_v3.py\n",
    "\n",
    "\n",
    "# BEGIN LINEITEM_V3\n",
    "class Quantity:  # <1>\n",
    "\n",
    "    def __init__(self, storage_name):\n",
    "        self.storage_name = storage_name  # <2>\n",
    "\n",
    "    def __set__(self, instance, value):  # <3>\n",
    "        if value > 0:\n",
    "            instance.__dict__[self.storage_name] = value  # <4>\n",
    "        else:\n",
    "            raise ValueError('value must be > 0')\n",
    "\n",
    "\n",
    "class LineItem:\n",
    "    weight = Quantity('weight')  # <5>\n",
    "    price = Quantity('price')  # <6>\n",
    "\n",
    "    def __init__(self, description, weight, price):  # <7>\n",
    "        self.description = description\n",
    "        self.weight = weight\n",
    "        self.price = price\n",
    "\n",
    "    def subtotal(self):\n",
    "        return self.weight * self.price\n",
    "# END LINEITEM_V3"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "A line item for a bulk food order has description, weight and price fields::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins = LineItem('Golden raisins', 10, 6.95)\n",
    "raisins.weight, raisins.description, raisins.price"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "A ``subtotal`` method gives the total price for that line item::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.subtotal()"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The weight of a ``LineItem`` must be greater than 0::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.weight = -20"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        ...\n",
    "    ValueError: value must be > 0\n",
    "\n",
    "Negative or 0 price is not acceptable either::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "truffle = LineItem('White truffle', 100, 0)"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        ...\n",
    "    ValueError: value must be > 0\n",
    "\n",
    "\n",
    "No change was made::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.weight"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "# %load ./bulkfood/bulkfood_v4.py\n",
    "\n",
    "\n",
    "# BEGIN LINEITEM_V4\n",
    "class Quantity:\n",
    "    __counter = 0  # <1>\n",
    "\n",
    "    def __init__(self):\n",
    "        cls = self.__class__  # <2>\n",
    "        prefix = cls.__name__\n",
    "        index = cls.__counter\n",
    "        self.storage_name = '_{}#{}'.format(prefix, index)  # <3>\n",
    "        cls.__counter += 1  # <4>\n",
    "\n",
    "    def __get__(self, instance, owner):  # <5>\n",
    "        return getattr(instance, self.storage_name)  # <6>\n",
    "\n",
    "    def __set__(self, instance, value):\n",
    "        if value > 0:\n",
    "            setattr(instance, self.storage_name, value)  # <7>\n",
    "        else:\n",
    "            raise ValueError('value must be > 0')\n",
    "\n",
    "\n",
    "class LineItem:\n",
    "    weight = Quantity()  # <8>\n",
    "    price = Quantity()\n",
    "\n",
    "    def __init__(self, description, weight, price):\n",
    "        self.description = description\n",
    "        self.weight = weight\n",
    "        self.price = price\n",
    "\n",
    "    def subtotal(self):\n",
    "        return self.weight * self.price\n",
    "# END LINEITEM_V4"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "A line item for a bulk food order has description, weight and price fields::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins = LineItem('Golden raisins', 10, 6.95)\n",
    "raisins.weight, raisins.description, raisins.price"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "A ``subtotal`` method gives the total price for that line item::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.subtotal()"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The weight of a ``LineItem`` must be greater than 0::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.weight = -20"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        ...\n",
    "    ValueError: value must be > 0\n",
    "\n",
    "No change was made::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.weight"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The value of the attributes managed by the descriptors are stored in\n",
    "alternate attributes, created by the descriptors in each ``LineItem``\n",
    "instance::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins = LineItem('Golden raisins', 10, 6.95)\n",
    "dir(raisins)  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "     'description', 'price', 'subtotal', 'weight']\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "getattr(raisins, '_Quantity#0')"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "getattr(raisins, '_Quantity#1')"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "# %load ./bulkfood/bulkfood_v4b.py\n",
    "\n",
    "\n",
    "# BEGIN LINEITEM_V4B\n",
    "class Quantity:\n",
    "    __counter = 0\n",
    "\n",
    "    def __init__(self):\n",
    "        cls = self.__class__\n",
    "        prefix = cls.__name__\n",
    "        index = cls.__counter\n",
    "        self.storage_name = '_{}#{}'.format(prefix, index)\n",
    "        cls.__counter += 1\n",
    "\n",
    "    def __get__(self, instance, owner):\n",
    "        if instance is None:\n",
    "            return self  # <1>\n",
    "        else:\n",
    "            return getattr(instance, self.storage_name)  # <2>\n",
    "\n",
    "    def __set__(self, instance, value):\n",
    "        if value > 0:\n",
    "            setattr(instance, self.storage_name, value)\n",
    "        else:\n",
    "            raise ValueError('value must be > 0')\n",
    "# END LINEITEM_V4B\n",
    "\n",
    "\n",
    "class LineItem:\n",
    "    weight = Quantity()\n",
    "    price = Quantity()\n",
    "\n",
    "    def __init__(self, description, weight, price):\n",
    "        self.description = description\n",
    "        self.weight = weight\n",
    "        self.price = price\n",
    "\n",
    "    def subtotal(self):\n",
    "        return self.weight * self.price"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "A line item for a bulk food order has description, weight and price fields::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins = LineItem('Golden raisins', 10, 6.95)\n",
    "raisins.weight, raisins.description, raisins.price"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "A ``subtotal`` method gives the total price for that line item::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.subtotal()"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The weight of a ``LineItem`` must be greater than 0::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.weight = -20"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        ...\n",
    "    ValueError: value must be > 0\n",
    "\n",
    "No change was made::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.weight"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The value of the attributes managed by the descriptors are stored in\n",
    "alternate attributes, created by the descriptors in each ``LineItem``\n",
    "instance::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins = LineItem('Golden raisins', 10, 6.95)\n",
    "dir(raisins)  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "     'description', 'price', 'subtotal', 'weight']\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "getattr(raisins, '_Quantity#0')"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "getattr(raisins, '_Quantity#1')"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "If the descriptor is accessed in the class, the descriptor object is\n",
    "returned:\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "LineItem.weight  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "LineItem.weight.storage_name"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "# %load ./bulkfood/bulkfood_v4c.py\n",
    "\n",
    "# BEGIN LINEITEM_V4C\n",
    "import model_v4c as model  # <1>\n",
    "\n",
    "\n",
    "class LineItem:\n",
    "    weight = model.Quantity()  # <2>\n",
    "    price = model.Quantity()\n",
    "\n",
    "    def __init__(self, description, weight, price):\n",
    "        self.description = description\n",
    "        self.weight = weight\n",
    "        self.price = price\n",
    "\n",
    "    def subtotal(self):\n",
    "        return self.weight * self.price\n",
    "# END LINEITEM_V4C"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "A line item for a bulk food order has description, weight and price fields::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins = LineItem('Golden raisins', 10, 6.95)\n",
    "raisins.weight, raisins.description, raisins.price"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "A ``subtotal`` method gives the total price for that line item::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.subtotal()"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The weight of a ``LineItem`` must be greater than 0::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.weight = -20"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        ...\n",
    "    ValueError: value must be > 0\n",
    "\n",
    "No change was made::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.weight"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The value of the attributes managed by the descriptors are stored in\n",
    "alternate attributes, created by the descriptors in each ``LineItem``\n",
    "instance::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins = LineItem('Golden raisins', 10, 6.95)\n",
    "dir(raisins)  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "     'description', 'price', 'subtotal', 'weight']\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "getattr(raisins, '_Quantity#0')"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "getattr(raisins, '_Quantity#1')"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "If the descriptor is accessed in the class, the descriptor object is\n",
    "returned:\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "LineItem.weight  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "LineItem.weight.storage_name"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "# %load ./bulkfood/bulkfood_v4prop.py\n",
    "\n",
    "\n",
    "# BEGIN LINEITEM_V4_PROP\n",
    "def quantity():  # <1>\n",
    "    try:\n",
    "        quantity.counter += 1  # <2>\n",
    "    except AttributeError:\n",
    "        quantity.counter = 0  # <3>\n",
    "\n",
    "    storage_name = '_{}:{}'.format('quantity', quantity.counter)  # <4>\n",
    "\n",
    "    def qty_getter(instance):  # <5>\n",
    "        return getattr(instance, storage_name)\n",
    "\n",
    "    def qty_setter(instance, value):\n",
    "        if value > 0:\n",
    "            setattr(instance, storage_name, value)\n",
    "        else:\n",
    "            raise ValueError('value must be > 0')\n",
    "\n",
    "    return property(qty_getter, qty_setter)\n",
    "# END LINEITEM_V4_PROP\n",
    "\n",
    "class LineItem:\n",
    "    weight = quantity()\n",
    "    price = quantity()\n",
    "\n",
    "    def __init__(self, description, weight, price):\n",
    "        self.description = description\n",
    "        self.weight = weight\n",
    "        self.price = price\n",
    "\n",
    "    def subtotal(self):\n",
    "        return self.weight * self.price\n",
    ""
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "A line item for a bulk food order has description, weight and price fields::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins = LineItem('Golden raisins', 10, 6.95)\n",
    "raisins.weight, raisins.description, raisins.price"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "A ``subtotal`` method gives the total price for that line item::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.subtotal()"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The weight of a ``LineItem`` must be greater than 0::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.weight = -20"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        ...\n",
    "    ValueError: value must be > 0\n",
    "\n",
    "No change was made::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.weight"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The value of the attributes managed by the descriptors are stored in\n",
    "alternate attributes, created by the descriptors in each ``LineItem``\n",
    "instance::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins = LineItem('Golden raisins', 10, 6.95)\n",
    "dir(raisins)  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "    'price', 'subtotal', 'weight']\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "getattr(raisins, '_quantity:0')"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "getattr(raisins, '_quantity:1')"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "# %load ./bulkfood/model_v4c.py\n",
    "# BEGIN MODEL_V4\n",
    "class Quantity:\n",
    "    __counter = 0\n",
    "\n",
    "    def __init__(self):\n",
    "        cls = self.__class__\n",
    "        prefix = cls.__name__\n",
    "        index = cls.__counter\n",
    "        self.storage_name = '_{}#{}'.format(prefix, index)\n",
    "        cls.__counter += 1\n",
    "\n",
    "    def __get__(self, instance, owner):\n",
    "        if instance is None:\n",
    "            return self\n",
    "        else:\n",
    "            return getattr(instance, self.storage_name)\n",
    "\n",
    "    def __set__(self, instance, value):\n",
    "        if value > 0:\n",
    "            setattr(instance, self.storage_name, value)\n",
    "        else:\n",
    "            raise ValueError('value must be > 0')\n",
    "# END MODEL_V4"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "# %load ./bulkfood/bulkfood_v5.py\n",
    "\n",
    "# BEGIN LINEITEM_V5\n",
    "import model_v5 as model  # <1>\n",
    "\n",
    "\n",
    "class LineItem:\n",
    "    description = model.NonBlank()  # <2>\n",
    "    weight = model.Quantity()\n",
    "    price = model.Quantity()\n",
    "\n",
    "    def __init__(self, description, weight, price):\n",
    "        self.description = description\n",
    "        self.weight = weight\n",
    "        self.price = price\n",
    "\n",
    "    def subtotal(self):\n",
    "        return self.weight * self.price\n",
    "# END LINEITEM_V5"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "A line item for a bulk food order has description, weight and price fields::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins = LineItem('Golden raisins', 10, 6.95)\n",
    "raisins.weight, raisins.description, raisins.price"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "A ``subtotal`` method gives the total price for that line item::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.subtotal()"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The weight of a ``LineItem`` must be greater than 0::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.weight = -20"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        ...\n",
    "    ValueError: value must be > 0\n",
    "\n",
    "No change was made::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins.weight"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The value of the attributes managed by the descriptors are stored in\n",
    "alternate attributes, created by the descriptors in each ``LineItem``\n",
    "instance::\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "raisins = LineItem('Golden raisins', 10, 6.95)\n",
    "dir(raisins)  # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "     'description', 'price', 'subtotal', 'weight']\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "getattr(raisins, '_Quantity#0')"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "getattr(raisins, '_NonBlank#0')"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "If the descriptor is accessed in the class, the descriptor object is\n",
    "returned:\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "LineItem.weight  # doctest: +ELLIPSIS"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "LineItem.weight.storage_name"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "The `NonBlank` descriptor prevents empty or blank strings to be used\n",
    "for the description:\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "br_nuts = LineItem('Brazil Nuts', 10, 34.95)\n",
    "br_nuts.description = ' '"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        ...\n",
    "    ValueError: value cannot be empty or blank\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "outputs": [],
   "execution_count": null,
   "cell_type": "code",
   "source": [
    "void = LineItem('', 1, 1)"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "        ...\n",
    "    ValueError: value cannot be empty or blank\n",
    "\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}